Skip to content

Conversation

@justin808
Copy link
Member

@justin808 justin808 commented Oct 29, 2025

Summary

  • Modified validate_local_paths! to show warnings instead of raising errors for missing directories
  • Updated swap_gemfile, swap_package_json, and build_local_packages! to skip missing paths with informative messages
  • NEW: Modified swap_gem_in_gemfile and swap_gem_to_github to allow re-swapping between different paths and branches
  • GitHub-managed repos are excluded from validation (they're cloned on demand)
  • Updated tests to reflect new warning and re-swapping behavior

Problem

  1. swap-deps would fail with an error if a local path reference in .swap-deps.yml or specified on the command line didn't exist. This was problematic in Conductor workflows where workspace directories and branches are frequently deleted after merging.

  2. Once a gem was swapped to a local path or GitHub branch, it couldn't be re-swapped to a different path/branch without first running --restore. This was inconvenient when switching between different Conductor workspaces or GitHub branches.

Solution

Graceful handling of missing directories

The tool now gracefully handles missing directories by:

  1. Showing a warning message listing all missing paths
  2. Skipping those paths during gem/package swapping
  3. Continuing to process other valid dependencies

Re-swapping support

The tool now supports re-swapping by:

  1. Replacing existing path: references with new paths
  2. Replacing existing github: and branch: references with new ones
  3. Allowing swaps from path: to github: and vice versa
  4. Preserving other gem options (like require: false) during re-swap

This enables workflows where developers can:

  • Switch between different Conductor workspace directories
  • Switch between different GitHub branches
  • Replace deleted directory references with new ones
  • All without needing to --restore first

Test plan

  • All existing RSpec tests pass (86 → 90 tests)
  • Added tests for re-swapping scenarios:
    • Path to different path
    • GitHub branch to different branch
    • Path to GitHub branch
    • GitHub branch to path
    • Preserving options during re-swap
  • Updated tests to verify warning behavior instead of error raising
  • RuboCop passes with no offenses
  • Pre-commit hooks pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a post-swap summary showing swapped vs. skipped dependencies.
  • Bug Fixes

    • Missing local dependency paths now emit warnings and are skipped (non-fatal); gems and npm packages are skipped with notices. Build/install steps tolerate absent local workspaces and proceed for available items.
  • UI / Status

    • Status heading renamed to "Dependencies:" and shows per-dependency details (branch indicators and repo badges); package.json swaps may be omitted from status in some cases.
  • Tests

    • Expanded tests for warning-based handling, status formatting, re-swap scenarios, and various Git/GitHub/local path transitions.

Previously, swap-deps would fail with an error if a local path reference
in .swap-deps.yml or specified on the command line didn't exist. This was
problematic in Conductor workflows where workspace directories and branches
are frequently deleted after merging.

Changes:
- Modified validate_local_paths! to show warnings instead of raising errors
  for missing directories
- Updated swap_gemfile, swap_package_json, and build_local_packages! to skip
  missing paths with informative messages
- GitHub-managed repos are excluded from validation (they're cloned on demand)
- Updated tests to reflect new warning behavior instead of error raising
- Fixed test stubs to allow additional File.exist? calls for git branch detection

This allows the tool to gracefully handle stale references and continue
processing other valid dependencies.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Oct 29, 2025

Walkthrough

validate_local_paths! now collects missing local gem/npm paths and emits a consolidated non-fatal warning instead of raising. Subsequent swap/build flows skip missing or GitHub-managed items, record skipped gems, and emit per-item notices; specs updated to expect warnings, "Dependencies:" labeling, and expanded re-swap/branch/ref handling.

Changes

Cohort / File(s) Summary
GemSwapper core
lib/demo_scripts/gem_swapper.rb
Added @skipped_gems tracking; validate_local_paths! collects missing local gem/npm paths and warns (no raise); skips GitHub-managed repos during validation; swap/build flows (swap_gemfile, swap_package_json, swap_gem_in_gemfile, swap_gem_to_github, build_local_packages!) skip missing local paths, record skipped gems, and emit per-item notices; added strip_gem_source_params and print_swap_summary.
Specs
spec/demo_scripts/gem_swapper_spec.rb
Expanded tests to expect non-fatal warnings for missing paths, "Dependencies:" status label, added re-swap scenarios (path↔github/git variants with branch/ref/tag/options), npm/malformed package.json and install/restore warning handling, and summary/skip messaging expectations.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant GemSwapper
    participant FileSystem

    rect rgb(200,220,255)
    note over GemSwapper: Old Flow — hard failure on missing local paths
    User->>GemSwapper: validate_local_paths!
    GemSwapper->>FileSystem: check gem/npm paths
    FileSystem-->>GemSwapper: some paths missing
    GemSwapper->>GemSwapper: raise DemoScripts::Error
    GemSwapper-->>User: ❌ Exception
    end

    rect rgb(220,255,220)
    note over GemSwapper: New Flow — collect, warn, skip missing paths
    User->>GemSwapper: validate_local_paths!
    GemSwapper->>FileSystem: check gem/npm paths
    FileSystem-->>GemSwapper: some paths missing
    GemSwapper->>GemSwapper: collect missing paths
    GemSwapper-->>User: ⚠️ Consolidated warning (missing paths)
    User->>GemSwapper: swap_gemfile / swap_package_json / build_local_packages!
    loop per dependency
      GemSwapper->>FileSystem: check dependency path
      alt path exists
        GemSwapper->>GemSwapper: perform swap/build
      else path missing
        GemSwapper-->>User: 📌 Skip notice (recorded in @skipped_gems)
      end
    end
    GemSwapper-->>User: ✅ print_swap_summary (swapped vs skipped)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review validate_local_paths! change and all call sites to ensure callers handle warnings rather than exceptions.
  • Verify strip_gem_source_params correctness and that option preservation/stripping in swap_gem_in_gemfile / swap_gem_to_github matches tests (branch/ref/tag handling).
  • Inspect npm-related flows and build_local_packages! for skipped-package handling and summary reporting.

Possibly related PRs

Poem

🐰
I hop through code with careful paws,
Missing paths earn gentle laws.
Warnings whisper, swaps proceed,
Skipped and swapped — the list we heed,
A tidy trail of tiny flaws.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Fix swap-deps to handle missing reference directories gracefully" is clearly and specifically related to the primary issue being addressed in this pull request. According to the PR objectives, the main problem was that swap-deps errored when local path references did not exist, breaking Conductor workflows where directories are frequently deleted. The pull request's central solution is to implement graceful handling of missing directories by emitting warnings instead of raising errors and continuing with valid dependencies, which is exactly what the title describes. The title is specific and descriptive rather than vague or generic, and it accurately reflects the main fix without being misleading or off-topic.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-swap-deps-missing-dir

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2201ad3 and c9065d7.

📒 Files selected for processing (2)
  • lib/demo_scripts/gem_swapper.rb (12 hunks)
  • spec/demo_scripts/gem_swapper_spec.rb (12 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
spec/demo_scripts/gem_swapper_spec.rb (1)
lib/demo_scripts/gem_swapper.rb (1)
  • include (14-1410)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: claude-review
🔇 Additional comments (16)
lib/demo_scripts/gem_swapper.rb (10)

41-41: LGTM!

The @skipped_gems tracking array is properly initialized to collect gems that cannot be swapped due to missing paths.


55-55: LGTM!

Adding print_swap_summary provides users with clear feedback on which gems were successfully swapped versus skipped.


814-839: LGTM!

The validation logic correctly:

  • Collects missing paths without failing
  • Excludes GitHub-managed repos from validation (they're cloned on demand)
  • Provides actionable guidance for users to fix missing paths

This graceful handling aligns with the PR objective to support Conductor workflows where directories may be deleted.


917-922: LGTM!

The skip logic correctly:

  • Verifies directory existence before swapping
  • Prevents duplicate entries in @skipped_gems
  • Provides clear skip messaging to users

964-983: LGTM!

The refactored swap_gem_in_gemfile correctly:

  • Handles existing path: and github: sources (enables re-swapping)
  • Uses strip_gem_source_params to preserve non-source options like require: false
  • Conditionally appends remaining options

This supports the PR objective of enabling re-swapping without requiring --restore first.


989-1011: LGTM!

The refactored swap_gem_to_github correctly:

  • Uses strip_gem_source_params to remove existing source specs
  • Preserves non-source options like require: false
  • Handles re-swapping from path, git URLs, or different GitHub branches
  • Correctly distinguishes between branch and tag references

1027-1032: LGTM!

The skip logic in swap_package_json is consistent with the swap_gemfile implementation and correctly handles missing npm package paths.


1324-1329: LGTM!

The skip logic in build_local_packages! consistently handles missing paths and prevents build attempts on non-existent directories.


1375-1387: LGTM!

The print_swap_summary method provides clear, actionable feedback by:

  • Showing successful swaps with counts and names
  • Highlighting skipped gems due to missing paths
  • Using consistent emoji indicators (✓ and ⊘)

941-956: The implementation is verified as correct with sufficient test coverage.

The regex patterns in strip_gem_source_params handle the scenarios you raised:

  1. Order sensitivity: Not an issue—each substitution is independent with its own leading comma pattern, so removing source params in any order produces the correct result.

  2. Version-only gems (gem 'name', '~> 1.0'): The first pattern /^\s*,\s*(['"])[^'"]*\1/ correctly strips the positional argument, returning an empty string. This is covered by the "with version constraint" and "with options but no version" tests.

  3. Mixed quote styles (gem 'name', "~> 1.0", require: false): The backreference \1 ensures the regex matches the same quote character that opened the string. This handles mixed quotes correctly and is validated through integration tests like "when swapped from git url with ref and options," which preserve require: false while removing git source params.

The test suite provides comprehensive coverage through swap_gem_in_gemfile and swap_gem_to_github integration tests, which validate the method's behavior across various real-world scenarios.

spec/demo_scripts/gem_swapper_spec.rb (6)

104-135: LGTM!

The test coverage for re-swapping scenarios is comprehensive:

  • Path-to-path re-swapping (lines 104-107)
  • GitHub-to-path conversion (lines 113-116)
  • GitHub branch-to-path conversion (lines 122-125)
  • Re-swapping with option preservation (lines 131-134)

These tests validate the PR objective of enabling re-swapping without requiring --restore.


154-190: LGTM!

Excellent test coverage for git URL conversions:

  • Git URL with ref (lines 157-159)
  • Git URL without ref (lines 166-168)
  • Git URL with branch (lines 175-177)
  • Git URL with ref and options (lines 186-188)

These tests ensure strip_gem_source_params correctly handles various git source specifications.


265-316: LGTM!

Comprehensive test coverage for GitHub re-swapping:

  • Branch-to-branch re-swapping (lines 269-272)
  • Path-to-GitHub conversion (lines 279-282)
  • Git URL to GitHub conversion (lines 289-292, 299-302)
  • Option preservation during conversion (lines 311-315)

These tests validate bidirectional re-swapping between local paths and GitHub sources.


361-439: LGTM!

Excellent test coverage for the new warning-based validation:

  • Basic warning test (lines 363-368)
  • Integration test validating warning → skip flow (lines 371-395)
  • Mixed valid/invalid paths scenario (lines 397-438)

The integration tests particularly well demonstrate the graceful handling of missing paths throughout the swap workflow.


950-950: LGTM!

The test correctly verifies that the enhanced error messaging includes the "Check Gemfile for errors" guidance when bundle install fails during restore.


1090-1213: LGTM!

The test updates correctly reflect the enhanced status output format:

  • Updated label from "Swapped dependencies:" to "Dependencies:" (lines 1102, 1121, 1141, 1191)
  • Verifies GitHub source indicators with [📦 repo] format (lines 1124-1125)
  • Verifies branch info display (lines 1123, 1142-1143, 1194)
  • Correctly documents that package.json-only swaps aren't shown in Gemfile status (lines 1159-1162)

The tests thoroughly validate the improved status display functionality.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
lib/demo_scripts/gem_swapper.rb (1)

1308-1312: Consider including path in skip message for consistency.

The skip logic correctly prevents build attempts on missing paths. The message format is slightly inconsistent with swap_gemfile (line 916) and swap_package_json (line 1014), which both include the full path. Including it here would maintain consistency and provide more context.

Apply this diff for consistency:

-        puts "  ⊘ Skipping #{gem_name} (path does not exist)"
+        puts "  ⊘ Skipping #{gem_name} (path does not exist: #{local_path})"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 026356b and c971db3.

📒 Files selected for processing (2)
  • lib/demo_scripts/gem_swapper.rb (8 hunks)
  • spec/demo_scripts/gem_swapper_spec.rb (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
spec/demo_scripts/gem_swapper_spec.rb (1)
lib/demo_scripts/gem_swapper.rb (1)
  • include (14-1379)
🔇 Additional comments (6)
lib/demo_scripts/gem_swapper.rb (3)

813-836: Well-designed graceful degradation.

The refactoring from hard failure to warning-based validation is well-executed. Collecting all missing paths upfront and providing clear remediation guidance (update .swap-deps.yml or use --restore) improves the user experience. Excluding GitHub-managed repos from validation (line 820) is correct since they're cloned on demand in clone_github_repos!.


914-918: Appropriate skip-on-missing behavior.

The directory check and skip logic correctly handles missing paths during the swap operation. This provides a second layer of validation beyond validate_local_paths!, which is important for TOCTOU scenarios where a directory might be deleted between validation and swap.


1012-1016: Consistent npm package handling.

The skip logic mirrors swap_gemfile, maintaining consistency across gem and npm package swapping operations.

spec/demo_scripts/gem_swapper_spec.rb (3)

252-259: Test correctly validates non-fatal warning behavior.

The updated test properly verifies that validate_local_paths! now emits a warning to stdout instead of raising an error when paths are missing.


771-771: Updated to match refined error message.

The test expectation correctly reflects the updated warning message in the implementation.


911-1050: Test updates properly reflect UI improvements.

The test changes correctly validate:

  1. Default mocks for File.exist? (lines 911, 970, 989) improve test isolation
  2. Updated status output heading from "Gemfile:" to "Dependencies:" (lines 923, 942, 1012)
  3. Enhanced GitHub repo display with branch info and repository badges (lines 943-946, 962-964)
  4. Refined messages distinguishing between "No swapped dependencies" and "No currently swapped dependencies (backups available)"

These changes align well with the implementation improvements in the main file.

@claude
Copy link

claude bot commented Oct 29, 2025

Code Review for PR #64: Fix swap-deps to handle missing reference directories gracefully

Summary

This PR improves the swap-deps tool by converting missing directory errors into warnings, allowing graceful handling of deleted Conductor workspaces or stale references. The changes are well-structured and address a real pain point in the workflow.

Strengths

1. Well-Designed Solution

  • The approach of warning-then-skipping is appropriate for this use case
  • Maintains backward compatibility while adding resilience
  • Clear separation between GitHub-managed repos (cloned on demand) and local paths

2. Good Error Handling

  • Informative warning messages with actionable guidance
  • Consistent skip messages across all affected methods
  • Properly handles the GitHub repos case by skipping validation (line 820)

3. Comprehensive Test Updates

  • Tests updated to verify new warning behavior instead of error raising
  • Test expectations adjusted to match new output format
  • Good coverage of the behavioral change

4. Code Quality

  • RuboCop compliance maintained
  • Clear comments explaining the rationale
  • Consistent messaging with emoji indicators

Suggestions and Considerations

1. Potential Logic Issue in validate_local_paths!

Location: lib/demo_scripts/gem_swapper.rb:820

Question: Is there a scenario where a gem could be in both gem_paths (with a local path) AND github_repos? If so, which takes precedence? Consider adding a comment clarifying the precedence.

2. Duplicate Directory Checks

After validate_local_paths! warns about missing paths, three other methods re-check File.directory? and print skip messages. Users may see duplicate messages. Consider tracking warned paths to suppress redundant skip messages, or keep as-is for clarity (acceptable).

3. Test Coverage Gap

Missing test case: Verify the interaction between validate_local_paths! and subsequent skip behavior. Current tests check them independently but not together.

4. Minor: Inconsistent Skip Messages

The skip messages have slight variations across methods. Consider standardizing the format for consistency.

Security

No security concerns identified. The changes maintain existing validation and don't introduce new risks.

Final Recommendation

APPROVE with minor suggestions. This is a solid PR that solves a real problem effectively. The suggestions above are minor improvements that could be addressed in a follow-up if desired. The core functionality is sound, well-tested, and follows the repository's conventions.

Great work on improving the developer experience!

Previously, swap-deps would skip gems that were already swapped (had path:
or github: in the Gemfile). This prevented updating to a different local
path or switching between branches after a branch was deleted.

Changes:
- Modified swap_gem_in_gemfile to replace existing path: or github:
  references instead of skipping them
- Modified swap_gem_to_github to replace existing path:, github:, branch:,
  and tag: references
- Both methods now preserve other gem options (like require: false) when
  re-swapping
- Added comprehensive tests for re-swapping scenarios:
  * Path to different path
  * GitHub branch to different branch
  * Path to GitHub branch
  * GitHub branch to path
  * Preserving options during re-swap

This enables workflows where developers switch between different Conductor
workspace directories or GitHub branches, even when the previous reference
no longer exists.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude
Copy link

claude bot commented Oct 29, 2025

Pull Request Review - PR #64

Summary

This PR improves the swap-deps tool's resilience by gracefully handling missing directories and enabling re-swapping between different paths/branches. The changes are well-tested with 4 new test cases added.

✅ Strengths

Code Quality

  • Clean refactoring: The transition from raising errors to showing warnings is well-implemented
  • Consistent patterns: Both swap_gem_in_gemfile and swap_gem_to_github follow similar refactoring patterns for re-swapping
  • Good separation of concerns: Validation logic is cleanly separated from swapping logic
  • RuboCop compliant: Proper use of disable/enable comments for legitimate complexity

Test Coverage

  • Excellent test additions: 4 new test scenarios for re-swapping (path-to-path, github-to-github, path-to-github, github-to-path)
  • Updated existing tests: Properly updated to reflect new warning behavior instead of error raising
  • Edge case coverage: Tests verify option preservation during re-swap
  • Test count increased: 86 → 90 tests, all passing

User Experience

  • Better workflow support: Allows developers to switch contexts without manual restore steps
  • Informative messaging: Clear warnings with actionable next steps
  • Non-breaking changes: Existing functionality preserved while adding flexibility

🔍 Detailed Analysis

1. Code Quality and Best Practices

validate_local_paths! (lib/demo_scripts/gem_swapper.rb:813-837)

  • ✅ Good: Accumulates missing paths before displaying warnings
  • ✅ Good: Skips GitHub-managed repos appropriately
  • ✅ Good: Provides helpful guidance in warning messages
  • ⚠️ Minor: Consider extracting warning message formatting to a separate method for better testability

swap_gem_in_gemfile and swap_gem_to_github (lib/demo_scripts/gem_swapper.rb:945-1005)

  • ✅ Good: Comprehensive regex substitution to remove all previous swap-related attributes
  • ✅ Good: Preserves options like require: false during re-swap
  • ✅ Good: Uses _match to explicitly ignore unused block parameter

2. Potential Bugs or Issues

Missing path handling

  • ✅ Good: Proper checks added before processing missing paths
  • ✅ Good: Clear skip messages with path information
  • ⚠️ Edge case: Paths that become available after validation will be skipped (acceptable behavior)

Regex patterns

  • ✅ Good: Covers path, github, branch, and tag attributes
  • ✅ Good: Handles both single and double quotes

3. Performance Considerations

  • ✅ Multiple regex substitutions have minimal impact (infrequent operations on small strings)
  • ✅ Validation loop is efficient (single pass, no unnecessary checks)

4. Security Concerns

  • ✅ No security issues identified
  • ✅ Path validation still occurs (warns instead of errors)
  • ✅ File operations remain safe and controlled

5. Test Coverage

New test cases (spec/demo_scripts/gem_swapper_spec.rb:101-135)

  • ✅ Excellent: Tests all re-swapping scenarios
  • ✅ Excellent: Verifies option preservation
  • ✅ Excellent: Tests both directions (path↔github)

📋 Recommendations

Medium Priority

  1. Documentation enhancement: Consider adding method comments explaining re-swapping behavior
  2. DRY the regex substitutions: Could extract to a helper method
  3. Consider integration test: Full end-to-end swap-then-re-swap test
  4. Warning format consistency: Use consistent visual indicators

🎯 Conclusion

Recommendation: ✅ APPROVE

This is a high-quality PR that:

  • Solves a real user pain point
  • Maintains backward compatibility
  • Has excellent test coverage
  • Follows Ruby and RuboCop best practices
  • Includes clear, helpful user messaging

The code is well-structured, the tests are comprehensive, and the implementation is robust. The suggestions above are optional improvements.

Great work, @justin808! This will significantly improve the developer experience when working with Conductor workspaces.


🤖 Generated with Claude Code

Changes:
- Added clarifying comments about github_repos precedence in validate_local_paths!
  GitHub repos take precedence and overwrite gem_paths entries after cloning
- Standardized skip message format across all methods:
  * swap_gemfile: "⊘ Skipping {gem} - path does not exist: {path}"
  * swap_package_json: "⊘ Skipping {gem} npm package - path does not exist: {path}"
  * build_local_packages!: "⊘ Skipping {gem} build - path does not exist: {path}"
- Added integration test verifying validate_local_paths! warns and subsequent
  swap operations skip with appropriate messages
- Updated comments to clarify that users are warned BEFORE skip messages appear

All 91 tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude
Copy link

claude bot commented Oct 29, 2025

Pull Request Review

Summary

This PR successfully transforms swap-deps from a strict fail-fast tool to a more flexible, workflow-friendly utility. The changes enable graceful handling of missing directories and support re-swapping dependencies without requiring --restore. Overall, this is a solid improvement that addresses real-world workflow pain points.


✅ Strengths

1. Well-Motivated Changes

The PR description clearly articulates the problem (Conductor workflow issues with deleted workspaces) and provides a comprehensive solution. The dual improvements (graceful missing path handling + re-swapping support) work well together.

2. Excellent Test Coverage

  • Test count increased from 86 to 90 tests
  • New tests comprehensively cover re-swapping scenarios (path to path, GitHub to GitHub, path to GitHub transitions)
  • Integration test validates warning + skip behavior flow (lines 300-323 in spec)
  • All existing tests updated to reflect new behavior

3. Consistent Implementation

The skip-on-missing-path pattern is consistently applied across swap_gemfile, swap_package_json, and build_local_packages\!

4. Good User Experience

  • Clear warning messages with actionable guidance
  • Skip messages use distinctive ⊘ symbol for visibility
  • Users warned once during validation, then operations proceed gracefully

🔍 Code Quality & Best Practices

Positive Observations:

  • RuboCop compliance: All style issues addressed with appropriate disables for complex methods
  • Good comments: The comment at lib/demo_scripts/gem_swapper.rb:819-821 explains GitHub repo precedence clearly
  • Regex improvements: The re-swapping logic properly strips ALL existing path/github/branch/tag options before replacing

Minor Concerns:

1. Regex Complexity in Option Stripping

The option extraction logic uses sequential .sub() calls which could be fragile. If options appear in unexpected orders or edge cases exist, this might not handle all scenarios correctly.

Suggestion: Consider extracting duplicate option-stripping logic to a helper method (it appears in both swap_gem_in_gemfile and swap_gem_to_github)

2. Output Consistency

The skip messages use puts while other error conditions use warn. Consider standardizing the output method for consistency.


🐛 Potential Bugs/Issues

1. Warning-Then-Skip Gap (Minor)

There's a small theoretical race condition if a directory is deleted between validate_local_paths\! and subsequent swap operations.

Impact: Low - unlikely in practice, and worst case is a file read error which Ruby handles gracefully.
Mitigation: Already implemented - each swap method checks File.directory? again.


⚡ Performance Considerations

No Performance Regression: The changes add minimal overhead

  • Directory checks are fast syscalls
  • Regex operations are only on small strings (gem lines)
  • No additional file I/O introduced

🔒 Security Concerns

✅ No Security Issues Identified

  1. Path Handling: Local paths are validated with File.directory? - no path traversal risks
  2. Regex Safety: The gem name is properly escaped with Regexp.escape(gem_name) in patterns
  3. No User Input: The changed code doesn't introduce new user input vectors
  4. File Operations: All file operations use existing safe helpers

📋 Test Coverage Assessment

Excellent Coverage:

  • ✅ Missing path validation warns instead of raising
  • ✅ Integration test validates warning → skip flow
  • ✅ Re-swap scenarios comprehensively tested
  • ✅ Option preservation during re-swap
  • ✅ Updated status output expectations

Suggestions for Additional Tests:

  1. Edge case testing for unusual gem line formatting
  2. Re-swap with multiple options (require: false, group: :development)
  3. Directory deletion between validation and swap (theoretical race condition)

📊 Overall Assessment

Category Rating Notes
Code Quality ⭐⭐⭐⭐ Well-structured, minor DRY opportunities
Test Coverage ⭐⭐⭐⭐⭐ Comprehensive, covers all new scenarios
Performance ⭐⭐⭐⭐⭐ No concerns, minimal overhead
Security ⭐⭐⭐⭐⭐ No issues identified
Documentation ⭐⭐⭐⭐ Clear PR description, good inline comments
Overall ⭐⭐⭐⭐½ Strong PR, ready to merge with minor suggestions

🎯 Recommendations

Required Before Merge:

  • None - PR is merge-ready as-is

Nice-to-Have Improvements:

  1. Extract duplicate option-stripping logic to helper method
  2. Add edge case tests for unusual gem line formatting
  3. Consider consistency: puts vs warn for skip messages

✅ Final Verdict

APPROVED - This PR successfully achieves its goals with solid implementation and excellent test coverage. The transformation from fail-fast to graceful degradation is exactly what Conductor workflows need. The re-swapping feature is implemented correctly and thoroughly tested.

The minor suggestions above are optimizations, not blockers. Great work! 🎉

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/demo_scripts/gem_swapper.rb (2)

949-968: Bundler breakage risk: don’t leave git:/ref: options when swapping to path.

If the original line had git: or ref:, the current regex doesn’t remove them. Those options are invalid for path: sources and can cause Gemfile parsing errors. Strip them alongside github/branch/tag.

Apply this diff in swap_gem_in_gemfile:

         options = rest.dup
         options = options.sub(/^\s*,\s*(['"])[^'"]*\1/, '') # Remove version if present
         options = options.sub(/,\s*path:\s*(['"])[^'"]*\1/, '') # Remove path: if present
+        options = options.sub(/,\s*git:\s*(['"])[^'"]*\1/, '') # Remove git: if present
         options = options.sub(/,\s*github:\s*(['"])[^'"]*\1/, '') # Remove github: if present
         options = options.sub(/,\s*branch:\s*(['"])[^'"]*\1/, '') # Remove branch: if present
         options = options.sub(/,\s*tag:\s*(['"])[^'"]*\1/, '') # Remove tag: if present
+        options = options.sub(/,\s*ref:\s*(['"])[^'"]*\1/, '') # Remove ref: if present

976-1004: Prevent conflicting options: strip git:/ref: when swapping to GitHub.

If the source had git: or ref: they remain, yielding conflicting params like github + branch + ref. Remove them like others.

Apply this diff in swap_gem_to_github:

       options = rest.dup
       options = options.sub(/^\s*,\s*(['"])[^'"]*\1/, '') # Remove version if present
       options = options.sub(/,\s*path:\s*(['"])[^'"]*\1/, '') # Remove path: if present
+      options = options.sub(/,\s*git:\s*(['"])[^'"]*\1/, '') # Remove git: if present
       options = options.sub(/,\s*github:\s*(['"])[^'"]*\1/, '') # Remove github: if present
       options = options.sub(/,\s*branch:\s*(['"])[^'"]*\1/, '') # Remove branch: if present
       options = options.sub(/,\s*tag:\s*(['"])[^'"]*\1/, '') # Remove tag: if present
+      options = options.sub(/,\s*ref:\s*(['"])[^'"]*\1/, '') # Remove ref: if present
🧹 Nitpick comments (3)
lib/demo_scripts/gem_swapper.rb (2)

1021-1025: Good: skip missing local paths for npm swaps; consider parse guard.

Behavior matches CLI warnings. Optional: rescue JSON::ParserError here (like detect_swapped_packages) to avoid aborting on malformed package.json.

Example:

   data = JSON.parse(File.read(package_json_path))
+ rescue JSON::ParserError
+   warn "  ⚠️  Warning: Could not parse #{File.basename(package_json_path)}; skipping npm swap"
+   return

386-413: Outdated-check on tags likely unreliable.

check_outdated_repos treats Git tags like branches, fetching origin/<tag> and diffing commits. That often fails or is meaningless for immutable tags.

  • Detect tag vs branch when parsing Gemfile (capture param name), store ref_type, and skip commit-behind checks for tags or display “n/a”.
spec/demo_scripts/gem_swapper_spec.rb (1)

44-153: Add tests to catch git:/ref: leftovers in Gemfile rewrites.

To guard against the critical issue flagged in the implementation, add cases ensuring these options are removed.

Example additions:

+    context 'when swapped from github with ref (commit)' do
+      let(:gemfile_content) { "gem 'shakapacker', github: 'user/repo', ref: 'abcdef'\n" }
+      it 'removes ref when converting to path' do
+        result = swapper.send(:swap_gem_in_gemfile, gemfile_content, 'shakapacker', local_path)
+        expect(result).to eq("gem 'shakapacker', path: '/Users/test/dev/shakapacker'\n")
+      end
+    end
+
+    context 'when converting from git url with ref' do
+      let(:gemfile_content) { "gem 'shakapacker', git: 'https://github.com/shakacode/shakapacker.git', ref: 'abcdef'\n" }
+      let(:github_info) { { repo: 'shakacode/shakapacker', branch: 'main' } }
+      it 'removes git/ref and uses github syntax' do
+        result = swapper.send(:swap_gem_to_github, gemfile_content, 'shakapacker', github_info)
+        expect(result).to eq("gem 'shakapacker', github: 'shakacode/shakapacker'\n")
+      end
+    end

Also applies to: 155-246

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c971db3 and 1769689.

📒 Files selected for processing (2)
  • lib/demo_scripts/gem_swapper.rb (9 hunks)
  • spec/demo_scripts/gem_swapper_spec.rb (11 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
spec/demo_scripts/gem_swapper_spec.rb (1)
lib/demo_scripts/gem_swapper.rb (1)
  • include (14-1388)
🔇 Additional comments (8)
lib/demo_scripts/gem_swapper.rb (3)

814-836: Graceful missing-path handling looks good.

Consolidated warning and non-fatal flow matches the PR intent. Skipping GitHub-managed repos avoids false positives.


914-919: Good: skip missing local paths in Gemfile swap.

Prevents crashes and keeps the run productive with clear notices.


1317-1321: Good: skip missing paths before building local packages.

Prevents needless npm work and noisy failures.

spec/demo_scripts/gem_swapper_spec.rb (5)

104-107: Re-swap coverage for GitHub→path and path→path looks solid.

These tests pin the intended idempotent rewrite behavior and option preservation.

Also applies to: 113-117, 119-126, 128-134


227-235: Re-swap to GitHub with branch updates is well covered.

Confirms branch replacement semantics and formatting.

Also applies to: 237-245


290-297: Warning-not-error expectation is correct for missing local paths.

Matches the new non-fatal flow.


947-964: Status output expectations updated appropriately.

Checks for “Dependencies:” header, GitHub badges, branch annotations, and backup summaries all align with the new UX.

Also applies to: 978-984, 1000-1003, 1019-1021, 1026-1056, 1068-1071


801-814: Restore path failure warnings validated.

Covers both the lock backup restoration and explicit “Check Gemfile for errors” hint.

@justin808
Copy link
Member Author

@claude review this PR

@claude
Copy link

claude bot commented Oct 29, 2025

Claude finished @justin808's task —— View job


Code Review for PR #64

Tasks

  • Read changed files to understand implementation
  • Analyze code changes and quality
  • Review test coverage
  • Formulate comprehensive feedback
  • Post final review

Summary

This PR successfully transforms the swap-deps tool from a fail-fast approach to a graceful degradation model. The implementation addresses real workflow pain points where Conductor workspaces are frequently deleted, and adds valuable re-swapping functionality. The changes are well-tested with 4 new test cases and comprehensive updates to existing tests.

Strengths

1. Well-Designed Solution ✅

  • Graceful degradation: Converting errors to warnings with skip behavior is appropriate for this use case
  • Clear user communication: Warning messages provide actionable guidance
  • Smart precedence handling: GitHub repos correctly skip validation since they're cloned on-demand (line 822)
  • Re-swapping support: Allows switching between paths/branches without restore - great UX improvement

2. Comprehensive Implementation ✅

  • Consistent patterns: All affected methods (swap_gemfile, swap_package_json, build_local_packages!) apply the same skip logic
  • Proper option preservation: Re-swapping correctly preserves gem options like require: false
  • Complete regex handling: Both swap_gem_in_gemfile and swap_gem_to_github strip all previous swap attributes (path, github, branch, tag)

3. Excellent Test Coverage ✅

  • 90 tests total (up from 86)
  • New re-swapping tests cover all combinations:
    • Path → different path (lines 101-107)
    • GitHub → different branch (lines 227-235)
    • Path → GitHub (lines 237-245)
    • GitHub → path (lines 110-117)
  • Option preservation verified (lines 128-135)
  • Integration test for warning + skip flow (lines 300-324)

4. Code Quality ✅

  • RuboCop compliant: Proper use of disable/enable comments
  • Clear comments: Lines 819-821 explain GitHub repo precedence
  • Consistent error handling: All skip messages use the same ⊘ symbol

Issues & Recommendations

🔴 Critical: Missing git: and ref: Options in Regex Stripping

Location: lib/demo_scripts/gem_swapper.rb:960-964 and 985-990

Issue: The regex substitutions remove path:, github:, branch:, and tag: but not git: or ref:. If a gem was previously using:

gem 'shakapacker', git: 'https://github.com/shakacode/shakapacker.git', ref: 'abc123'

After swapping to a path, you'd get:

gem 'shakapacker', path: '/path', git: '...', ref: '...'  # Invalid!

Impact: This will cause Bundler parsing errors.

Recommendation: Add these two lines to both methods:

options = options.sub(/,\s*git:\s*(['"])[^'"]*\1/, '')   # Remove git: if present
options = options.sub(/,\s*ref:\s*(['"])[^'"]*\1/, '')   # Remove ref: if present

Test Gap: No test coverage for swapping from git: or ref: sources.


⚠️ Medium: Duplicate Skip Messages

Location: Multiple methods check File.directory? after validate_local_paths!

Issue: Users see the warning once during validation, then see skip messages for each operation (swap_gemfile, swap_package_json, build_local_packages!). This could be noisy.

Current behavior (acceptable):

⚠️  Warning: Some local paths do not exist:
   shakapacker: /path/to/missing

Processing demo...
  ⊘ Skipping shakapacker - path does not exist: /path/to/missing
  ⊘ Skipping shakapacker npm package - path does not exist: /path/to/missing
  ⊘ Skipping shakapacker build - path does not exist: /path/to/missing

Recommendation: This is acceptable as-is for clarity, but could be optimized by tracking warned paths if it becomes annoying in practice.


⚠️ Medium: Test Coverage for git: and ref: Options

Location: spec/demo_scripts/gem_swapper_spec.rb

Missing tests:

  1. Swapping from gem 'name', git: 'url', ref: 'sha' to path
  2. Swapping from gem 'name', git: 'url' to GitHub spec
  3. Preserving other options when removing git/ref

Recommendation: Add test cases like:

context 'when swapped from git url with ref' do
  let(:gemfile_content) { "gem 'shakapacker', git: 'https://github.com/user/repo.git', ref: 'abc123'\n" }
  
  it 'removes git and ref when converting to path' do
    result = swapper.send(:swap_gem_in_gemfile, gemfile_content, 'shakapacker', local_path)
    expect(result).to eq("gem 'shakapacker', path: '/Users/test/dev/shakapacker'\n")
  end
end

Minor Observations

1. Documentation Quality ✅

The PR description is excellent with clear problem/solution sections and comprehensive test plan.

2. Backward Compatibility ✅

Changes are non-breaking - existing workflows continue to work while adding resilience.

3. Performance ✅

Multiple File.directory? checks have negligible impact - they're fast syscalls and only run during swap operations.

4. Security ✅

  • Path validation still occurs (warns instead of errors)
  • Regexp.escape(gem_name) prevents injection
  • No new user input vectors introduced

Recommendations Summary

Priority Issue Action Required
🔴 Critical Missing git: and ref: option stripping Add two more .sub() calls in both swap methods
⚠️ Medium Missing test coverage for git/ref scenarios Add 2-3 test cases
💡 Optional Duplicate skip messages Consider tracking warned paths (low priority)

Final Verdict

Recommendation: ✅ APPROVE with required fix

This PR delivers significant value by solving real workflow problems. The implementation is solid, test coverage is comprehensive, and the code quality is high.

Before merging: Please add the git: and ref: option stripping to prevent Bundler errors. This is a quick 2-line fix in each method.

After merging: Consider adding the missing test cases for git/ref scenarios to prevent regressions.

Great work on improving the developer experience! The re-swapping feature and graceful missing path handling will be very useful. 🎉


Problem: When swapping from gems using git: urls (e.g.,
gem 'name', git: 'url', ref: 'sha'), the git: and ref: parameters
were not being removed, resulting in invalid Gemfile entries like:
gem 'name', path: '/path', git: 'url', ref: 'sha'

This would cause Bundler parsing errors.

Changes:
- Added git: and ref: removal to swap_gem_in_gemfile regex substitutions
- Added git: and ref: removal to swap_gem_to_github regex substitutions
- Updated comments to reflect all source specification parameters are removed
- Added 7 comprehensive test cases:
  * Swapping from git: url with ref: to path
  * Swapping from git: url without ref: to path
  * Swapping from git: url with branch: to path
  * Swapping from git: url with ref: and options to path (preserving options)
  * Swapping from git: url with ref: to github
  * Swapping from git: url with branch: to github
  * Swapping from git: url with ref: and options to github (preserving options)

All 98 tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude
Copy link

claude bot commented Oct 30, 2025

PR Review: Fix swap-deps to handle missing reference directories gracefully

Overall Assessment

This is a well-implemented PR that addresses real-world pain points in the swap-deps workflow. The changes are well-tested, thoughtfully designed, and include excellent documentation. The code quality is high with proper RuboCop compliance and comprehensive test coverage.

Code Quality & Best Practices ✅

Strengths:

  • Excellent test coverage: Increased from 86 to 90 tests with comprehensive scenarios
  • Clean separation of concerns: The validation and swap logic are properly separated
  • RuboCop compliant: Proper use of disable/enable comments for legitimate complexity
  • Clear user feedback: Informative warning messages and skip notices

Best Practices Followed:

  • DRY principle with regex pattern reuse
  • Consistent error handling across methods
  • Proper use of early returns for clarity
  • Well-commented code explaining complex logic

Potential Issues & Recommendations 🔍

1. Regex Duplication (Minor)

The option-stripping regex in swap_gem_in_gemfile and swap_gem_to_github are identical. Consider extracting to a helper method to reduce maintenance burden.

2. Silent Skip Behavior (Design Question)

When paths are missing, the tool skips them after initial warning. Consider adding a summary at the end showing what was swapped vs. skipped to make this more visible.

3. Edge Case: Empty Options String (Low Risk)

If options becomes just whitespace after stripping, you might end up with trailing commas. Consider trimming the final options string.

4. Test Integration Gap (Minor)

The integration test validates validation to skip flow nicely, but doesn't verify that valid paths are still processed correctly when some paths are missing. Consider adding a test with mixed valid/invalid paths.

Performance Considerations ⚡

  • Early validation: Missing paths identified once at start
  • Efficient regex operations: gsub with block is appropriate
  • No unnecessary I/O: File checks performed only when needed
  • Repeated File.directory? checks are necessary since file system state could change

Security Considerations 🔒

  • Proper escaping: Regexp.escape prevents regex injection
  • No shell injection risks: File paths used directly in Ruby operations
  • No eval/exec of user input: All string operations use safe methods
  • Git operations use array form to avoid shell injection

Test Coverage ✅

Excellent coverage with tests for:

  • Path to different path re-swapping
  • GitHub branch to different branch re-swapping
  • Cross-type swapping (path ↔ github)
  • Option preservation during re-swap
  • Git URL with ref, branch, tag handling
  • Warning behavior for missing paths
  • Integration test for validation + skip workflow
  • Status display updates

Minor gaps: Mixed valid/invalid path scenarios, npm package re-swapping

Final Verdict

Approve with minor suggestions. This PR successfully solves the stated problems with a clean implementation and strong test coverage. The suggested improvements are minor and don't block merging. Great work! 🎉

1. Extract Duplicate Regex Stripping (DRY Principle)
   - Created strip_gem_source_params helper method to eliminate code duplication
   - Removes: version, path, github, git, branch, tag, ref parameters
   - Returns empty string if only whitespace remains, or options with leading comma
   - Fixes edge case: trailing commas from empty options after stripping

2. Add Swap Summary Output
   - Tracks skipped gems in @skipped_gems array
   - New print_swap_summary method shows:
     * Number of successfully swapped gems (with names)
     * Number of skipped gems due to missing paths (with names)
   - Makes silent skip behavior more visible to users

3. Comprehensive Integration Test
   - Added test for mixed valid/invalid paths scenario
   - Verifies:
     * Validation warns only about invalid paths
     * Valid paths are processed correctly
     * Invalid paths are skipped with appropriate messages
     * Gemfile content reflects only valid swaps

All 99 tests pass.

Benefits:
- Reduced maintenance burden (single source of truth for regex patterns)
- Better user visibility into what was swapped vs skipped
- More robust test coverage for real-world scenarios

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude
Copy link

claude bot commented Nov 1, 2025

Pull Request Review

Overall Assessment ✅

This is a well-executed PR that successfully addresses the problem of handling missing local paths in the swap-deps tool. The changes are thoughtful, well-tested, and improve the developer experience significantly.


Code Quality & Best Practices ⭐

Strengths:

  1. Clean separation of concerns - The new strip_gem_source_params method properly extracts the logic for removing source specification parameters
  2. Proper RuboCop compliance - All complexity warnings are appropriately disabled with rubocop:disable comments where necessary
  3. Consistent error handling - The graceful degradation pattern (warn but continue) is applied consistently across all methods
  4. Good code reuse - The strip_gem_source_params method is shared between swap_gem_in_gemfile and swap_gem_to_github
  5. State tracking - The @skipped_gems instance variable properly tracks which gems were skipped for summary reporting

Suggestions:

  1. strip_gem_source_params method (lib/demo_scripts/gem_swapper.rb:946-955): Consider using a more maintainable approach with a loop or array of parameters to remove. This makes it easier to add/remove parameters in the future.

Potential Bugs or Issues 🔍

Minor Concerns:

  1. Race condition potential (lib/demo_scripts/gem_swapper.rb:817-826): The validation checks File.directory?(path), then later methods check again. If a directory is deleted between these checks, the skip message would appear multiple times. Consider caching the validation results.

  2. Regex edge case (lib/demo_scripts/gem_swapper.rb:946-955): The current regex for stripping parameters might fail with nested quotes or escaped quotes. However, this is an edge case and unlikely in practice.

  3. Missing validation (lib/demo_scripts/gem_swapper.rb:917-921): The skip logic doesn't validate if local_path is nil before calling File.directory?. Though unlikely given the code flow, defensive programming would add a nil check.


Performance Considerations ⚡

Good:

  • Early returns in validation prevent unnecessary processing
  • File system checks are only done once per path during validation
  • The @skipped_gems array prevents duplicates

Potential Optimizations:

  1. Multiple file system checks - File.directory? is called during validation AND during each swap operation. Consider storing validation results to avoid repeated I/O.

  2. Array lookup performance - @skipped_gems.include?(gem_name) is O(n). For large gem lists, consider using a Set.


Security Considerations 🔒

Strengths:

  1. ✅ No shell injection vulnerabilities - The code properly uses array form for git commands elsewhere in the file
  2. ✅ Path validation prevents directory traversal issues
  3. ✅ GitHub repo validation exists

No security concerns identified - The changes don't introduce any new security vulnerabilities.


Test Coverage 🧪

Excellent test coverage!

The PR increases test count from 86 to 90 tests and covers:

  • ✅ Re-swapping from path to different path
  • ✅ Re-swapping from github branch to different branch
  • ✅ Re-swapping from path to github and vice versa
  • ✅ Re-swapping with preserved options (require: false)
  • ✅ Re-swapping from git URLs with various ref types (ref, branch, tag)
  • ✅ Warning behavior for missing paths
  • ✅ Skip behavior during swap operations
  • ✅ Mixed valid/invalid path scenarios
  • ✅ Summary output formatting

Test Quality:

  • Tests use proper mocking/stubbing patterns
  • Tests verify both stdout output and file content changes
  • Integration tests verify the full flow from validation to swap
  • Edge cases are well covered

Minor Suggestion:
Consider adding a test for the edge case where a directory is deleted between validation and swap operations to ensure graceful handling.


Summary

Approval Status: ✅ Approved

This PR demonstrates excellent software engineering practices:

  • Clear problem identification and solution
  • Comprehensive test coverage
  • Backward compatibility maintained
  • Good user experience (informative warnings, helpful messages)
  • Clean code with appropriate complexity management

The suggestions above are minor optimizations and defensive programming practices that could be addressed in a follow-up PR if desired. They don't block this PR from merging.

Recommendation: Merge after CI passes.

Great work on improving the developer experience for Conductor workflows! 🎉


Review generated with Claude Code

@justin808 justin808 merged commit 0e76ec6 into main Nov 1, 2025
2 checks passed
@justin808 justin808 deleted the fix-swap-deps-missing-dir branch November 1, 2025 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants